# UserInterface - Main window of the Entertainer frontend
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import os
import gtk
import time
import clutter
from clutter import cluttergtk

# Utility classes
from frontend.user_event import UserEvent
from frontend.media_player import MediaPlayer
from frontend.gui.widgets.desktop import Desktop
from frontend.gui.screen_history import ScreenHistory
from frontend.gui.frontend_window import FrontendWindow
from frontend.gui.widgets.menu_overlay import MenuOverlay

# Transition effects
from frontend.gui.transitions.transition import Transition
from frontend.gui.transitions.fade_transition import FadeTransition
from frontend.gui.transitions.slide_transition import SlideTransition
from frontend.gui.transitions.no_effect_transition import NoEffectTransition
from frontend.gui.transitions.zoom_and_fade_transition import ZoomAndFadeTransition
from frontend.gui.transitions.slide_and_fade_transition import SlideAndFadeTransition

# Screens
from frontend.gui.screens.screen import Screen
from frontend.gui.screens.main_screen import MainScreen
from frontend.gui.screens.disc_screen import DiscScreen

# Dialogs
from frontend.gui.screens.question_dialog import QuestionDialog

# RSS Screens
from frontend.gui.screens.rss_screen import RssScreen
from frontend.gui.screens.feed_screen import FeedScreen
from frontend.gui.screens.feed_entry_screen import FeedEntryScreen

# Photograph library
from frontend.gui.screens.photo_screen import PhotoScreen
from frontend.gui.screens.photographs_screen import PhotographsScreen
from frontend.gui.screens.photoalbums_screen import PhotoAlbumsScreen

# Music library
from frontend.gui.screens.music_screen import MusicScreen
from frontend.gui.screens.artist_screen import ArtistScreen
from frontend.gui.screens.album_screen import AlbumScreen
from frontend.gui.screens.audio_play_screen import AudioPlayScreen

# Video library
from frontend.gui.screens.video_screen import VideoScreen
from frontend.gui.screens.movie_screen import MovieScreen
from frontend.gui.screens.tv_serie_screen import TVSerieScreen
from frontend.gui.screens.tv_serie_episodes_screen import TVSerieEpisodesScreen

# Television
from frontend.gui.screens.tv_osd_screen import TvOSDScreen

# Weather
from frontend.gui.screens.weather_screen import WeatherScreen

# OSD Screens
from frontend.gui.screens.video_osd_screen import VideoOSDScreen

class UserInterface:
    """
    Entertainer frontend user interface.

    Object from this class represents the main window of the Entertainer client.
    This window is actually a wrapper for Pigment viewport.
    """
    
    def __init__(self, frontend):
        """
        Initialize user interface.
        @param frontend: FrontendClient object
        """
        #self.STAGE_WIDTH = 1920
        #self.STAGE_HEIGHT = 1080
        
        #self.STAGE_WIDTH = 1680
        #self.STAGE_HEIGHT = 945
        
        self.STAGE_WIDTH = 1366
        self.STAGE_HEIGHT = 768
        
        #self.STAGE_WIDTH = 800
        #self.STAGE_HEIGHT = 450
        
        self.frontend = frontend
        self.theme = frontend.getTheme()
        self.logger = frontend.get_logger()
        
        # Set desktop resolution width and height to class attributes
        rwindow = gtk.gdk.get_default_root_window()
        self.res_width, self.res_height = rwindow.get_size()
        self.fullscreen_offset = int((self.res_height - self.res_width * 0.5625) / 2) # Keep 16:9
        
        # Create stage
        self.stage = clutter.Stage()
        #self.stage.set_user_resizable(True)
        self.stage.connect('key-press-event', self.handle_keyboard_event)
        #self.stage.connect('notify::scale-x', self._size_changed)
        #self.stage.connect('notify::scale-y', self._size_changed)
        self.stage.set_color(self.theme.getColor("background"))
        self.stage.set_size(self.STAGE_WIDTH, self.STAGE_HEIGHT)
        self.stage.set_title("Entertainer")
        
        self.is_fullscreen = False
        
        #self.desktop = Desktop()
        #self.desktop.set_position(0,0)
        #self.desktop.show()
        #self.stage.add(self.desktop)
        
        # Initialize Screen history (allows user to navigate "back")
        self.history = ScreenHistory(self.stage, 
                                     frontend.get_configuration().history_size()) 
        
        # Intialize media player
        self.player = MediaPlayer(self.stage, 
                                  self.STAGE_WIDTH, 
                                  self.STAGE_HEIGHT)
        
        # Initialize menu overlay texture
        self.overlay_status = False
        self.menu_overlay = MenuOverlay(self.theme)
        self.menu_overlay.set_opacity(0)
        self.menu_overlay.set_name("overlay")
        self.menu_overlay.show()
        self.menu_overlay.set_size(self.STAGE_WIDTH, self.STAGE_HEIGHT)
        self.stage.add(self.menu_overlay)
        
        # Transition object. Handels effects between screen changes.
        if self.frontend.get_configuration().show_effects():
            self.transition = self.createTransition(self.frontend.get_configuration().transition_effect())
        else:
            self.transition = self.createTransition("No effect")
        
        # Embed Stage into GTK-window
        #self.window = FrontendWindow(self.frontend)
        #gtkwidget = cluttergtk.Embed()
        #self.window.setClutterWidget(gtkwidget)
        #self.resizeStage(800,450)
        #self.resizeStage(1366,768)

        # Black bars that are used if monitor/TV aspect ratio is not 16:9
        self.top_bar = clutter.Rectangle()
        self.top_bar.set_size(self.res_width, self.fullscreen_offset)
        self.top_bar.set_color(clutter.Color(0,0,0,255))
        self.top_bar.set_position(0,0)
        self.stage.add(self.top_bar)
        
        self.bottom_bar = clutter.Rectangle()
        self.bottom_bar.set_size(self.res_width, self.fullscreen_offset)
        self.bottom_bar.set_color(clutter.Color(0,0,0,255))
        self.bottom_bar.set_position(0, int(self.res_width * 0.5625 - self.fullscreen_offset))
        self.stage.add(self.bottom_bar)
        print "Resolution height - offset: " + str( self.res_height - self.fullscreen_offset)
        self.logger.debug("Frontend GUI initialized succesfully")
    
    # This will probably be needed eventually to fix the exiting bug
    #def delete_event(self, widget, event, data=None):
        #self.shutdown()
    
    def _size_changed(self, stage, x):
        """
        Callback funcion for stage resize.
        @param stage: clutter.Stage object
        @param x: UNKNOWN  
        """
        #FIXME: Implement and figure out how to do this right way
        print x.blurb
        print x.default_value
        print x.epsilon
        print x.flags
        print x.maximum
        print x.minimum
        print x.name
        print x.nick
        print x.owner_type
        print x.value_type
    
    def start_up(self, fullscreen):
        """
        This is called once when Entertainer is starting up.
        @param fullscreen: Should we start in fullscreen mode
        """
        self.show()
        self.current = self.createScreen("main")
        self.transition.forwardEffect(None, self.current)
        self.enable_menu_overlay()
    
    def shutdown(self):
        """
        This is called once when Entertainer is shuting down.
        """
        self.frontend.quit_frontend()
        
    def get_abs_x_pos(self, percent):
        """
        Transform relative position to absolute position of the screen.
        @param percent: float value between 0-1
        @return absolute X coordinate
        """
        return int(float(self.STAGE_WIDTH) * percent)
    
    def get_abs_y_pos(self, percent):
        """
        Transform relative position to absolute position of the screen.
        @param percent: float value between 0-1
        @return absolute Y coordinate
        """
        return int(float(self.STAGE_HEIGHT) * percent)

    def x(self, percent):
        """
        Transform relative position to absolute position of the screen.
        @param percent: float value between 0-1
        @return absolute X coordinate (can be used as width as well)
        """
        return int(float(self.STAGE_WIDTH) * percent)
    
    def y(self, percent):
        """
        Transform relative position to absolute position of the screen.
        @param percent: float value between 0-1
        @return absolute Y coordinate  (can be used as height as well)
        """
        return int(float(self.STAGE_HEIGHT) * percent)

    def getStage(self):
        """
        Get user interface stage.
        @return - Stage object (of Clutter)
        """
        return self.stage
    
    def get_media_player(self):
        """
        Get media player object
        @return: MediaPlayer object
        """
        return self.player
    
    def toggle_fullscreen(self):
        """
        Set UI in fullscreen mode.
        """
        if self.is_fullscreen:
            self.stage.unfullscreen()
            self.top_bar.hide()
            self.bottom_bar.hide()
            self.resizeStage(self.STAGE_WIDTH, self.STAGE_HEIGHT)
            self.current.get_group().set_position(0, 0)
            self.menu_overlay.set_position(0, 0)
            self.is_fullscreen = False
        else:
            self.stage.fullscreen()
            self.top_bar.show()
            self.bottom_bar.show()
            self.resizeStage(self.res_width, self.res_width * 0.5625)
            self.current.get_group().set_position(0, self.fullscreen_offset)
            self.menu_overlay.set_position(0, self.fullscreen_offset)
            self.is_fullscreen = True
    
    def resizeStage(self, width, height):
        """
        Resize and scale stage.
        @param width: New width for the stage
        @param height: New height for the stage
        """
        self.stage.set_scale(float(width) / float(self.STAGE_WIDTH), 
                               float(height) / float(self.STAGE_HEIGHT))
        self.stage.set_size(int(width), int(height))
    
    def createScreen(self, type, data=None):
        """
        Factory method for screens. Create a Screen object of given type.
        
        @param type: Type of the screen as a string.
        @param data: Data can contain some screen spesific data
        """
        if type == "main":
            return MainScreen(self, self.frontend)
        elif type == "audio_cd":
            return DiscScreen(self, self.frontend)
        elif type == "rss":
            return RssScreen(self, self.frontend, self.frontend.getTheme())
        elif type == "feed":
            return FeedScreen(self, self.frontend, data)
        elif type == "entry":
            return FeedEntryScreen(self, self.frontend, data)
        elif type == "video_osd":
            return VideoOSDScreen(self, self.frontend.getTheme(), self.player)
        elif type == "tv_osd":
            return TvOSDScreen(self, self.frontend.getTheme(), self.player)
        elif type == "photo_albums":
            return PhotoAlbumsScreen(self, self.frontend)
        elif type == "photographs":
            return PhotographsScreen(self, self.frontend, data)
        elif type == "photo":
            return PhotoScreen(self, self.frontend, data)
        elif type == "music":
            return MusicScreen(self, self.frontend)
        elif type == "artist":
            return ArtistScreen(self, self.frontend, data)
        elif type == "music_album":
            return AlbumScreen(self, self.frontend, data)
        elif type == "audio_play":
            return AudioPlayScreen(self, self.frontend, data)
        elif type == "videos":
            return VideoScreen(self, self.frontend)
        elif type == "movie":
            return MovieScreen(self, self.frontend, data)
        elif type == "tv-serie":
            return TVSerieScreen(self, self.frontend, data)
        elif type =="tv-episodes":
            return TVSerieEpisodesScreen(self, self.frontend, data)
        elif type == "weather":
            return WeatherScreen(self, self.frontend)
        elif type == "question_dialog":
            return QuestionDialog(self, self.frontend, data)
    
    def createTransition(self, type):
        """
        Factory method for transitions. Create a Transition object of given type.
        @param type: Transition type as a string value.
        """
        if type == "No effect":
            return NoEffectTransition(self.stage)
        elif type == "Crossfade":
            return FadeTransition(self.stage)
        elif type =="Zoom and fade":
            return ZoomAndFadeTransition(self.stage)
        elif type =="Slide and fade":
            return SlideAndFadeTransition(self.stage)
        elif type == "Slide":
            return SlideTransition(self.stage)
        
    def show(self):
        """
        Show user interface.
        """
        self.stage.show()
        #self.window.show()
        
    def enable_menu_overlay(self):
        """
        Enable menu overlay. Overlay should be enabled always when there is
        a video playing and menu showing at the same time. Overlay is not part
        of any specific screen. It is used for all screens when neccesary.
        """
        self.overlay_status = True
        if self.frontend.get_configuration().show_effects():
            self.menu_overlay.fade_in()
        else:
            self.menu_overlay.set_opacity(255)
        
    def disable_menu_overlay(self):
        """
        Disable menu overlay. Overlay should be disabled when current screen is
        a type of Screen.OSD.
        """
        self.overlay_status = False
        if self.frontend.get_configuration().show_effects():
            self.menu_overlay.fade_out()
        else:
            self.menu_overlay.set_opacity(0)
             
    def is_overlay(self):
        """
        Get menu overlay status.
        @return boolean value. True if menu overlay is active, otherwise False.
        """
        return self.overlay_status
    
    def changeScreen(self, screen, direction):
        """
        Execute screen change.
        @param screen - Screen object. This screen is displayed after change.
        @param direction - Should we use forward or backward effect
        """
        # Enable/Disable menu overlay
        if screen.get_type() == Screen.OSD and self.is_overlay():
            self.disable_menu_overlay()
        elif screen.get_type() != Screen.OSD and not self.is_overlay():
            self.enable_menu_overlay()

        # Add current screen to screen history
        if direction == Transition.FORWARD and self.current.get_type() != Screen.DIALOG:
            self.history.add_screen(self.current)
            
        # Change screen (Logical). Graphics is changed via animation
        from_screen = self.current
        self.current = screen
        
        # Animate screen change
        if direction == Transition.FORWARD:
            self.transition.forwardEffect(from_screen, screen)
        elif direction == Transition.BACKWARD:
            self.transition.backwardEffect(from_screen, screen)
        
    def handle_keyboard_event(self, stage, event):
        """
        Translate all received keyboard events to Entertainer's UserEvents.
        UserEvent's are device independent high level events.
        @param stage: Stage object which received key pressing event
        @param event: Event object (from clutter framework)
        """
        if event.keyval == clutter.keysyms.Return: # Enter key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_SELECT))
        elif event.keyval == clutter.keysyms.Up: # Up arrow key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_UP))
        elif event.keyval == clutter.keysyms.Down: # Down arrow key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_DOWN))
        elif event.keyval == clutter.keysyms.Left: # Left arrow key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_LEFT))
        elif event.keyval == clutter.keysyms.Right: # Right arrow key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_RIGHT))
        elif event.keyval == clutter.keysyms.BackSpace: # Backspace key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_BACK))
        elif event.keyval == clutter.keysyms.h: # H key
            self.handle_user_event(UserEvent(UserEvent.NAVIGATE_HOME))
        elif event.keyval == clutter.keysyms.f: # F key
            self.handle_user_event(UserEvent(UserEvent.TOGGLE_FULLSCREEN))
        elif event.keyval == clutter.keysyms.p: # P key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_PLAY_PAUSE))
        elif event.keyval == clutter.keysyms.s: # S key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_STOP))
        elif event.keyval == clutter.keysyms._1: # 1 key
            self.handle_user_event(UserEvent(UserEvent.USE_ASPECT_RATIO_1))
        elif event.keyval == clutter.keysyms._2: # 2 key
            self.handle_user_event(UserEvent(UserEvent.USE_ASPECT_RATIO_2))
        elif event.keyval == clutter.keysyms._3: # 3 key
            self.handle_user_event(UserEvent(UserEvent.USE_ASPECT_RATIO_3))
        elif event.keyval == clutter.keysyms._4: # 4 key
            self.handle_user_event(UserEvent(UserEvent.USE_ASPECT_RATIO_4))   
        elif event.keyval == clutter.keysyms.x: # X key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_SKIP_BACKWARD))
        elif event.keyval == clutter.keysyms.c: # C key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_SKIP_FORWARD))
        elif event.keyval == clutter.keysyms.z: # Z key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_PREVIOUS))
        elif event.keyval == clutter.keysyms.v: # V key
            self.handle_user_event(UserEvent(UserEvent.PLAYER_NEXT))    
        elif event.keyval == clutter.keysyms.q or event.keyval == clutter.keysyms.Escape: # Q / ESC key
            self.handle_user_event(UserEvent(UserEvent.QUIT_FRONTEND))
            
    def handle_user_event(self, event):
        """
        Handle used event. This function is called every time that Entertainer
        receives event from the user. This event can be from keyboard, remote
        control, gtk-widget or some other source.
        @param event: UserEvent object, defines what action has been taken
        """
        type = event.get_type()
        
        if(type == UserEvent.PLAYER_PLAY_PAUSE):
            if self.current.is_interested_in_play_action():
                self.current.execute_play_action()
            else:
                if self.player.is_playing():
                    self.player.pause()
                    self.current.handle_user_event(event)
                else:
                    self.player.play()
                    self.current.handle_user_event(event)
                    
        elif(type == UserEvent.PLAYER_STOP):
            if self.player.is_playing():
                self.player.stop()
                self.current.handle_user_event(event)
                
        elif(type == UserEvent.PLAYER_NEXT):
            self.player.next()
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.PLAYER_PREVIOUS):
            self.player.previous()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.PLAYER_SKIP_FORWARD):
            self.player.skip_forward()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.PLAYER_SKIP_BACKWARD):
            self.player.skip_backward()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.NAVIGATE_UP):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.NAVIGATE_DOWN):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.NAVIGATE_LEFT):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.NAVIGATE_RIGHT):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.NAVIGATE_HOME):
            self.changeScreen(self.createScreen("main"), Transition.FORWARD)
            
        elif(type == UserEvent.NAVIGATE_CURRENTLY_PLAYING):
            pass
        
        elif(type == UserEvent.NAVIGATE_BACK):
            if not self.history.is_empty() and self.current.get_type() != Screen.DIALOG:
                screen = self.history.get_screen()
                screen.update()
                self.changeScreen(screen, Transition.BACKWARD)
            
        elif(type == UserEvent.NAVIGATE_SELECT):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.NAVIGATE_CONTENT):
            self.current.handle_user_event(event)
        
        elif(type == UserEvent.TOGGLE_FULLSCREEN):
            #self.window.toggle_fullscreen(None)
            self.toggle_fullscreen()
            
        elif(type == UserEvent.USE_ASPECT_RATIO_1):
            self.player.set_native_ratio()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.USE_ASPECT_RATIO_2):
            self.player.set_widescreen_ratio()
            self.current.handle_user_event(event)

        elif(type == UserEvent.USE_ASPECT_RATIO_3):
            self.player.set_zoom_ratio()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.USE_ASPECT_RATIO_4):
            self.player.set_intelligent_ratio()
            self.current.handle_user_event(event)
            
        elif(type == UserEvent.USE_NEXT_ASPECT_RATIO):
            pass
        
        elif(type == UserEvent.QUIT_FRONTEND):
            self.shutdown()
            